package mysql

import (
	"errors"
	"fmt"
	_ "github.com/go-sql-driver/mysql"
	"github.com/jmoiron/sqlx"
	"sync"
	"time"
	"wzgames/pkg/config"
)

var (
	errNotFoundDatabaseSource = errors.New("database source Not Found")

	pbdb   *dss
	dbonce = sync.Once{}
)

type DsConf struct {
	DriverName      string
	DataSourceName  string
	MaxOpenConns    int
	MaxIdleConns    int
	ConnMaxLifetime time.Duration // idletimeout
}

type dss struct {
	datasources *sync.Map
	dsConfs     *sync.Map
}

type DS struct {
	dsname string
	db     *sqlx.DB

	closed bool
}

func NewDB(name string, confData config.ConfData) (*DS, error) {
	if pbdb == nil {
		dbonce.Do(func() {
			pbdb = &dss{
				datasources: &sync.Map{},
				dsConfs:     &sync.Map{},
			}
		})
	}

	return pbdb.createDS(name, confData)
}

func (dm *dss) createDS(name string, confData config.ConfData) (*DS, error) {
	if confData == nil {
		return nil, fmt.Errorf("database source=%s create Error on nil configor", name)
	}
	c := dm.parseConfig(confData)
	if db, ok := dm.datasources.Load(name); ok { // datasource exists
		return db.(*DS).UpdatePool(name, c)
	}
	return dm.create(name, c)
}

func (dm *dss) create(dsname string, conf DsConf) (*DS, error) {
	db, err := sqlx.Connect(conf.DriverName, conf.DataSourceName)
	if err != nil {
		return nil, err
	}
	db.SetMaxOpenConns(conf.MaxOpenConns)
	db.SetMaxIdleConns(conf.MaxIdleConns)
	if conf.ConnMaxLifetime > 0 {
		db.SetConnMaxLifetime(conf.ConnMaxLifetime)
	}
	rds := &DS{
		dsname: dsname,
		db:     db,
	}
	dm.datasources.Store(dsname, rds)
	dm.dsConfs.Store(dsname, conf)
	return rds, nil
}

func (dm *dss) parseConfig(confData config.ConfData) DsConf {
	driverName := confData.GetString("driverName", "mysql")
	dataSourceName := confData.GetString("dataSourceName", "")
	if driverName == "" || dataSourceName == "" {
		panic("parse database conf fatal")
	}
	maxOpenConns := confData.GetInt("maxOpenConns", 50)
	maxIdleConns := confData.GetInt("maxIdleConns", 50)
	connMaxLifetime := confData.GetInt("connMaxLifetime", 0)

	return DsConf{
		DriverName:      driverName,
		DataSourceName:  dataSourceName,
		MaxOpenConns:    maxOpenConns,
		MaxIdleConns:    maxIdleConns,
		ConnMaxLifetime: time.Second * time.Duration(connMaxLifetime),
	}
}

func (dm *dss) Get(name string) *DS {
	if p, ok := dm.datasources.Load(name); !ok {
		panic(errNotFoundDatabaseSource)
	} else {
		return p.(*DS)
	}
}

func (dm *dss) Destroy() {
	dm.datasources.Range(func(key, v any) bool {
		err := v.(*DS).Destroy()
		if err != nil {
		}
		return true
	})
}

// db
func (db *DS) UpdatePool(dsname string, conf DsConf) (*DS, error) {
	oldCfg, ok := pbdb.dsConfs.Load(dsname)

	if ok {
		oldConfig := oldCfg.(DsConf)
		if oldConfig.DriverName != conf.DriverName || oldConfig.DataSourceName != conf.DataSourceName {
			err := db.Destroy()
			if err != nil {
			}
			pbdb.datasources.Delete(dsname)
			pbdb.dsConfs.Delete(dsname)
			return pbdb.create(dsname, conf)
		}

		if conf.MaxOpenConns > 0 && oldConfig.MaxOpenConns != conf.MaxOpenConns {
			db.db.SetMaxOpenConns(conf.MaxOpenConns)
		}
		if conf.MaxIdleConns > 0 && oldConfig.MaxIdleConns != conf.MaxIdleConns {
			db.db.SetMaxIdleConns(conf.MaxIdleConns)
		}
		if conf.ConnMaxLifetime > 0 && oldConfig.ConnMaxLifetime != conf.ConnMaxLifetime {
			db.db.SetConnMaxLifetime(conf.ConnMaxLifetime)
		}
	}

	pbdb.dsConfs.Delete(dsname)
	pbdb.dsConfs.Store(dsname, conf)

	return db, nil
}

func (db *DS) ParseConfig(confData config.ConfData) DsConf {
	return pbdb.parseConfig(confData)
}

func (db *DS) Ds() *sqlx.DB {
	return db.db
}

func (db *DS) Destroy() error {
	if db.closed {
		return nil
	}
	db.closed = true
	return db.db.Close()
}
